package tclog

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"path"
	"runtime"
	"strings"
	"sync"
	"time"
)

func New(logfile string, showallpath bool) *TLogGo {
	obj := create(nil, ``, Ldate|Ltime|Lmicroseconds|Llongfile)
	obj.logFileName = logfile
	obj.SetLogFileMaxSize(30)
	obj.checkLogFile()
	obj.SetLogOnConsle(true)
	obj.logLevel = TLevelALL
	if showallpath {
		obj.SetFlags(Llongfile)
	} else {
		obj.SetFlags(Lshortfile)
	}
	return obj
}

func (self *TLogGo) backupLog() {
	if self.logFileName == `` {
		return
	}
	currTime := time.Now()
	year, month, day := currTime.Date()
	hour, min, sec := currTime.Clock()
	microSec := currTime.UTC().Nanosecond() / 1000
	sNow := fmt.Sprintf("%0.4d%0.2d%0.2d%0.2d%0.2d%0.2d%0.6d",
		year, month, day, hour, min, sec, microSec)
	sExt := path.Ext(self.logFileName)
	sBack := strings.TrimRight(self.logFileName, sExt)
	sBack = sBack + sNow + sExt
	fmt.Println(sBack)
	os.Rename(self.logFileName, sBack)
}

//返回日志文件的大小(单位是字节)
func (self *TLogGo) getFileSize() int64 {
	if self.logFileName == `` {
		return 0
	}
	vFileInfo, vErr := os.Stat(self.logFileName)
	if vErr != nil && !os.IsExist(vErr) {
		return 0
	}
	return vFileInfo.Size()
}

func (self *TLogGo) checkLogFile() {
	self.checkLogLock.Lock()
	self.checkLogLock.Unlock()
	nCurrSize := self.getFileSize()
	if nCurrSize > self.fileMaxSize {
		self.logFilePtr.Close()
		self.logFilePtr = nil
		self.backupLog()
	}

	if self.logFilePtr != nil {
		return
	}
	var vErr error
	if self.logFileName != `` {
		self.logFilePtr, vErr = os.OpenFile(self.logFileName, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0660)
		if vErr != nil {
			panic(vErr)
		}
	} else {
		self.logFilePtr = os.Stderr
	}
	self.setOutput(self.logFilePtr)
}

const (
	TLevelOFF = iota
	TLevelFATAL
	TLevelERROR
	TLevelWARN
	TLevelINFO
	TLevelDEBUG
	TLevelALL
)

///////////////////////////////////////////////////////////////////////////////

const (
	Ldate         = 1 << iota     // the date in the local time zone: 2009/01/23
	Ltime                         // the time in the local time zone: 01:23:23
	Lmicroseconds                 // microsecond resolution: 01:23:23.123123.  assumes Ltime.
	Llongfile                     // full file name and line number: /a/b/c/d.go:23
	Lshortfile                    // final file name element and line number: d.go:23. overrides Llongfile
	LUTC                          // if Ldate or Ltime is set, use UTC rather than the local time zone
	LstdFlags     = Ldate | Ltime // initial values for the standard logger
)

type TLogGo struct {
	mu     sync.Mutex // ensures atomic writes; protects the following fields
	prefix string     // prefix to write at beginning of each line
	flag   int        // properties
	out    io.Writer  // destination for output
	buf    []byte     // for accumulating text to write
	////////////////////////////
	logFileName  string
	logFilePtr   *os.File
	logOnConsle  bool  //日志是否显示在控制台上
	logLevel     int   //日志记录级别
	fileMaxSize  int64 //日志文件最大体积(单位是字节)
	checkLogLock sync.Mutex
}

func (self *TLogGo) SetLogOnConsle(v bool) {
	self.logOnConsle = v
}

func (self *TLogGo) SetLogLevel(v int) {
	self.logLevel = v
}

//参数单数为兆
func (self *TLogGo) SetLogFileMaxSize(v int) {
	self.fileMaxSize = int64(v * 1024 * 1024)
}

func create(out io.Writer, prefix string, flag int) *TLogGo {
	return &TLogGo{out: out, prefix: prefix, flag: flag}
}

func (l *TLogGo) setOutput(w io.Writer) {
	l.mu.Lock()
	defer l.mu.Unlock()
	l.out = w
}

func itoa(buf *[]byte, i int, wid int) {
	// Assemble decimal in reverse order.
	var b [20]byte
	bp := len(b) - 1
	for i >= 10 || wid > 1 {
		wid--
		q := i / 10
		b[bp] = byte('0' + i - q*10)
		bp--
		i = q
	}
	// i < 10
	b[bp] = byte('0' + i)
	*buf = append(*buf, b[bp:]...)
}

func (l *TLogGo) formatHeader(buf *[]byte, t time.Time, file string, line int) {
	if l.flag&LUTC != 0 {
		t = t.UTC()
	}
	if l.flag&(Ldate|Ltime|Lmicroseconds) != 0 {
		//		*buf = append(*buf, '[')
		if l.flag&Ldate != 0 {
			year, month, day := t.Date()
			itoa(buf, year, 4)
			*buf = append(*buf, '-')
			itoa(buf, int(month), 2)
			*buf = append(*buf, '-')
			itoa(buf, day, 2)
			*buf = append(*buf, ' ')
		}
		if l.flag&(Ltime|Lmicroseconds) != 0 {
			hour, min, sec := t.Clock()
			itoa(buf, hour, 2)
			*buf = append(*buf, ':')
			itoa(buf, min, 2)
			*buf = append(*buf, ':')
			itoa(buf, sec, 2)
			if l.flag&Lmicroseconds != 0 {
				*buf = append(*buf, '.')
				itoa(buf, t.Nanosecond()/1e3, 6)
			}
			*buf = append(*buf, ' ')
		}
		*buf = bytes.Trim(*buf, " ")
		*buf = append(*buf, " "...)
	}
	if l.flag&(Lshortfile|Llongfile) != 0 {
		if l.flag&Lshortfile != 0 {
			short := file
			for i := len(file) - 1; i > 0; i-- {
				if file[i] == '/' {
					short = file[i+1:]
					break
				}
			}
			file = short
		}
		*buf = append(*buf, "["+file...)
		*buf = append(*buf, ':')
		itoa(buf, line, -1)
		*buf = append(*buf, "]"...)
	}
	*buf = append(*buf, l.prefix...)
}

// Output writes the output for a logging event.  The string s contains
// the text to print after the prefix specified by the flags of the
// TLogGo.  A newline is appended if the last character of s is not
// already a newline.  Calldepth is used to recover the PC and is
// provided for generality, although at the moment on all pre-defined
// paths it will be 2.
func (l *TLogGo) output(calldepth int, level int, s string) error {
	////////////////////////////////////////////////////////////////////////////
	l.checkLogFile()
	l.mu.Lock()
	defer func() {
		l.logFilePtr.Close()
		l.logFilePtr = nil
		l.mu.Unlock()
	}()
	////////////////////////////////////////////////////////////////////////////
	now := time.Now() // get this early.
	var file string
	var line int
	if l.flag&(Lshortfile|Llongfile) != 0 {
		// release lock while getting caller info - it's expensive.
		l.mu.Unlock()
		var ok bool
		_, file, line, ok = runtime.Caller(calldepth)
		if !ok {
			file = "???"
			line = 0
		}
		l.mu.Lock()
	}
	switch level {
	case TLevelDEBUG:
		l.prefix = `[debug]`
	case TLevelINFO:
		l.prefix = `[info]`
	case TLevelWARN:
		l.prefix = `[warn]`
	case TLevelERROR:
		l.prefix = `[error]`
	case TLevelFATAL:
		l.prefix = `[fatal]`
	default:
		l.prefix = `[none]`
	}
	l.buf = l.buf[:0]
	l.formatHeader(&l.buf, now, file, line)
	l.buf = append(l.buf, s...)
	if len(s) == 0 || s[len(s)-1] != '\n' {
		l.buf = append(l.buf, '\n')
	}
	_, err := l.out.Write(l.buf)
	////////////////////////////////////////////////////////////////////////////
	if l.out != os.Stderr && l.logOnConsle {
		fmt.Print(string(l.buf))
	}
	////////////////////////////////////////////////////////////////////////////
	return err
}

func (l *TLogGo) Printf(format string, v ...interface{}) {
	if l.logLevel < TLevelINFO {
		return
	}
	l.output(2, TLevelINFO, fmt.Sprintf(format, v...))
}

func (l *TLogGo) Print(v ...interface{}) {
	if l.logLevel < TLevelINFO {
		return
	}
	l.output(2, TLevelINFO, fmt.Sprint(v...))
}

func (l *TLogGo) Println(v ...interface{}) {
	if l.logLevel < TLevelINFO {
		return
	}
	l.output(2, TLevelINFO, fmt.Sprintln(v...))
}

func (l *TLogGo) Debugf(format string, v ...interface{}) {
	if l.logLevel < TLevelDEBUG {
		return
	}
	l.output(2, TLevelDEBUG, fmt.Sprintf(format, v...))
}

func (l *TLogGo) Debug(v ...interface{}) {
	if l.logLevel < TLevelDEBUG {
		return
	}
	l.output(2, TLevelDEBUG, fmt.Sprint(v...))
}

func (l *TLogGo) Debugln(v ...interface{}) {
	if l.logLevel < TLevelDEBUG {
		return
	}
	l.output(2, TLevelDEBUG, fmt.Sprintln(v...))
}

func (l *TLogGo) Infof(format string, v ...interface{}) {
	if l.logLevel < TLevelINFO {
		return
	}
	l.output(2, TLevelINFO, fmt.Sprintf(format, v...))
}

func (l *TLogGo) Info(v ...interface{}) {
	if l.logLevel < TLevelINFO {
		return
	}
	l.output(2, TLevelINFO, fmt.Sprint(v...))
}

func (l *TLogGo) Infoln(v ...interface{}) {
	if l.logLevel < TLevelINFO {
		return
	}
	l.output(2, TLevelINFO, fmt.Sprintln(v...))
}

func (l *TLogGo) Warnf(format string, v ...interface{}) {
	if l.logLevel < TLevelWARN {
		return
	}
	l.output(2, TLevelWARN, fmt.Sprintf(format, v...))
}

func (l *TLogGo) Warn(v ...interface{}) {
	if l.logLevel < TLevelWARN {
		return
	}
	l.output(2, TLevelWARN, fmt.Sprint(v...))
}

func (l *TLogGo) Warnln(v ...interface{}) {
	if l.logLevel < TLevelWARN {
		return
	}
	l.output(2, TLevelWARN, fmt.Sprintln(v...))
}

func (l *TLogGo) Errorf(format string, v ...interface{}) {
	if l.logLevel < TLevelERROR {
		return
	}
	l.output(2, TLevelERROR, fmt.Sprintf(format, v...))
}

func (l *TLogGo) Error(v ...interface{}) {
	if l.logLevel < TLevelERROR {
		return
	}
	l.output(2, TLevelERROR, fmt.Sprint(v...))
}

func (l *TLogGo) Errorln(v ...interface{}) {
	if l.logLevel < TLevelERROR {
		return
	}
	l.output(2, TLevelERROR, fmt.Sprintln(v...))
}

// Fatal is equivalent to l.Print() followed by a call to os.Exit(1).
func (l *TLogGo) Fatal(v ...interface{}) {
	if l.logLevel < TLevelFATAL {
		return
	}
	l.output(2, TLevelFATAL, fmt.Sprint(v...))
	os.Exit(1)
}

func (l *TLogGo) Fatalf(format string, v ...interface{}) {
	if l.logLevel < TLevelFATAL {
		return
	}
	l.output(2, TLevelFATAL, fmt.Sprintf(format, v...))
	os.Exit(1)
}

func (l *TLogGo) Fatalln(v ...interface{}) {
	if l.logLevel < TLevelFATAL {
		return
	}
	l.output(2, TLevelFATAL, fmt.Sprintln(v...))
	os.Exit(1)
}

func (l *TLogGo) Flags() int {
	l.mu.Lock()
	defer l.mu.Unlock()
	return l.flag
}

func (l *TLogGo) SetFlags(flag int) {
	l.mu.Lock()
	defer l.mu.Unlock()
	l.flag |= flag
}

func (l *TLogGo) Prefix() string {
	l.mu.Lock()
	defer l.mu.Unlock()
	return l.prefix
}

func (l *TLogGo) SetPrefix(prefix string) {
	l.mu.Lock()
	defer l.mu.Unlock()
	l.prefix = prefix
}
